#include "bootloader.h"
#include "flash_if.h"
#include "crc32.h"
#include "upgrade.h"

void JumpToApplication(void)
{
	uint32_t JumpAddress = 0;
	pFunction Application ;
	
	if (((*(__IO uint32_t*)(FLASH_APP_REGION_START_ADDR + MCU_FIRMWARE_HEADER_LEN)) & 0x2FFE0000 ) == 0x20000000)
	{
		 /* Jump to user application */
      JumpAddress = *(__IO uint32_t*) (FLASH_APP_REGION_START_ADDR + MCU_FIRMWARE_HEADER_LEN + 4);
      Application = (pFunction) JumpAddress;
      /* Initialize user application's Stack Pointer */
      __set_MSP(*(__IO uint32_t*) (FLASH_APP_REGION_START_ADDR + MCU_FIRMWARE_HEADER_LEN));
      Application();
	}
}


/*
 * @brief  check firmware version is equal
 * @param  application region flash address
 * @param  upload region flash address
 * @retval version is equal ?
*/
uint32_t check_firmware_version(uint32_t app_addr, uint32_t upload_addr)
{
	mcu_img_header_t *p_app = (mcu_img_header_t *)app_addr;
	mcu_img_header_t *p_upload = (mcu_img_header_t *)upload_addr;
	
	if (p_app->img_crc == p_upload->img_crc)
		return MCU_FIRMWARE_VRESION_EQUAL;
	else
		return MCU_FIRMWARE_VRESION_NOT_EQUAL;
}

/*
 * @brief  check firmware header 
 * @param  flash region  address
 * @retval crc check is ok ?
*/
uint32_t check_firmware_header_crc(uint32_t addr)
{
	mcu_img_header_t *pheader = (mcu_img_header_t *)addr;
	uint32_t cal_crc = 0;
	
	cal_crc = crc32((unsigned char *)pheader, MCU_FIRMWARE_HEADER_WITHOUT_CRC_LEN);
	if (cal_crc == pheader->header_crc)
		return MCU_FIRMWARE_HEADER_CRC_OK;
	else
		return MCU_FIRMWARE_HEADER_CRC_FAIL;
}

uint32_t check_firmware_image_crc(uint32_t addr)
{
	mcu_img_header_t *pheader = (mcu_img_header_t *)addr;
	uint32_t image_len = 0;
	uint32_t cal_image_crc = 0;
	
	image_len = pheader->len;
	
	if (image_len > MAX_FIRMWARE_LEN)
		return MCU_FIRMWARE_IMAGE_CRC_FAIL;
	
	cal_image_crc = crc32(((unsigned char *)(addr + MCU_FIRMWARE_HEADER_LEN)), image_len);
	
	if (cal_image_crc == pheader->img_crc)
		return MCU_FIRMWARE_IMAGE_CRC_OK;
	else
		return MCU_FIRMWARE_IMAGE_CRC_FAIL;
}

uint32_t copy_data_to_application(void)
{
	int i = 0;
	uint32_t write_len = 0;
	uint32_t rc = FLASH_WRITE_PROGRAM_FAIL;
	mcu_img_header_t *pheader = (mcu_img_header_t *)FLASH_UPLOAD_REGION_START_ADDR;
	
	write_len = (pheader->len + MCU_FIRMWARE_HEADER_LEN) / sizeof(uint64_t);
	
	/* Erase application region */
	FLASH_If_Erase(FLASH_APP_REGION_START_ADDR, FLASH_APP_PAGE_NUM);
	
	/* copy three times max  */
	for (i = 3; i > 0; i--)
	{
		if (FLASH_If_Write(FLASH_APP_REGION_START_ADDR, (uint64_t *)pheader,
			write_len)	== FLASH_WRITE_SUCCESS)
		{
			rc = FLASH_WRITE_SUCCESS;
			break;
		}
	}
	
	return rc;
}

uint32_t check_firmware_data(uint32_t addr)
{
    if (check_firmware_header_crc(addr) != MCU_FIRMWARE_HEADER_CRC_OK)
	{
		return MCU_FIRMWARE_CHECK_FAIL;
	}
    
    if (check_firmware_image_crc(addr) != MCU_FIRMWARE_IMAGE_CRC_OK)
    {
        return MCU_FIRMWARE_CHECK_FAIL;
    }
    
    return MCU_FIRMWARE_CHECK_OK;
}

void boot_from_appliction(uint32_t app_stat, uint32_t upload_stat)
{
    if (app_stat == MCU_FIRMWARE_CHECK_OK)
    {
        /* check ok , run from application */
        JumpToApplication();
    }
    
    /* check not ok. copy data from upload region */
    if (upload_stat == MCU_FIRMWARE_CHECK_OK)
    {
        if (copy_data_to_application() == FLASH_WRITE_SUCCESS)
        {
            JumpToApplication();
        }
        else
        {
            wait_for_upgrade();
        }
    }
    wait_for_upgrade();
}

void mcu_firmware_bootloader(void)
{
    uint32_t app_data_check_stat = 0;
    uint32_t upload_data_check_stat = 0;
    
    app_data_check_stat = check_firmware_data(FLASH_APP_REGION_START_ADDR);
    upload_data_check_stat = check_firmware_data(FLASH_UPLOAD_REGION_START_ADDR);
    
    if (check_firmware_version(FLASH_APP_REGION_START_ADDR, FLASH_UPLOAD_REGION_START_ADDR) \
        == MCU_FIRMWARE_VRESION_EQUAL)
    {
        /* Version is equal */
        boot_from_appliction(app_data_check_stat,upload_data_check_stat);
    }
    else /* need upload application */
    {
        if (app_data_check_stat == MCU_FIRMWARE_CHECK_OK 
            && upload_data_check_stat != MCU_FIRMWARE_CHECK_OK) // upload region is not ok
        {
            JumpToApplication();
        }
        else if (upload_data_check_stat == MCU_FIRMWARE_CHECK_OK) //upload region is ok
        {
            /* copy upload region data to application */
            if (copy_data_to_application() == FLASH_WRITE_SUCCESS)
                JumpToApplication();
            else //copy fail
                wait_for_upgrade();
        }
        else // upload region and application are both not ok
        {
            wait_for_upgrade();
        }
    }
}


